/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2021年10月25日
 */
package com.massyframework.beanskin.scriptengine.beancontext.provider;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.massyframework.beanskin.assembly.Assembly;
import com.massyframework.beanskin.assembly.handling.EnvironmentHandler;
import com.massyframework.beanskin.assembly.handling.ReadyingException;
import com.massyframework.beanskin.assembly.handling.ReadyingHandler;
import com.massyframework.beanskin.service.ServiceReference;
import com.massyframework.beanskin.service.ServiceRegistration;
import com.massyframework.beanskin.service.ServiceRegistry;
import com.massyframework.beanskin.service.ServiceRepository;
import com.massyframework.beanskin.web.servlet.DelegateServlet;
import com.massyframework.beanskin.web.utils.ServletRegistrationUtils;

/**
 * 基于Servlet模式下的脚本引擎上下文初始化器
 * @author huangkh
 *
 */
class ScriptEngineServletContextInitializer extends ScriptEngineContextInitalizer
	implements EnvironmentHandler, ReadyingHandler{

	private static Logger LOG = LoggerFactory.getLogger(ScriptEngineServletContextInitializer.class);
	private volatile boolean runAtJ2EE = true;
    private volatile ServiceRegistration<Servlet> servletRegistration;
    
    private volatile ServletConfig config;
    
	/**
	 * 
	 */
	public ScriptEngineServletContextInitializer() {
		
	}

	@Override
	protected void init() throws Exception {
		super.init();
		
		final Assembly assembly = this.getAssociatedAssembly();
        ServiceRepository repository = this.getServiceRepository(assembly);

        Optional<ServletContext> optional = repository.findService(ServletContext.class);
        ServletException ex = optional.map( context -> {
            this.runAtJ2EE = true;
            try {
                ServletRegistrationUtils.createServletRegistration(
                        context,
                        assembly,
                        null,
                        "true");
            }catch(ServletException e){
                return e;
            }
            return null;
        }).orElse(null);

        if (ex != null){
            throw ex;
        }
	}

	/* (non-Javadoc)
     * @see com.massyframework.assembly.spi.ReadyingHandler#readying()
     */
    @Override
    public synchronized void readying() throws ReadyingException {
        if (!this.runAtJ2EE) {
            return;
        }

        this.exportService();
    }

    /**
     * 输出服务
     */
    protected synchronized void exportService() {
        if (this.servletRegistration == null) {
        	Assembly assembly = this.getAssociatedAssembly();
            ServiceRepository repository = this.getServiceRepository(assembly);
            ServiceRegistry registry = repository.getServiceRegistry();

            Bridge servlet = new Bridge();
            Map<String, Object> props = new HashMap<>();
            props.put(ServiceReference.SERVICE_NAME, assembly.getConfig().getParameter("servlet.name"));

            this.servletRegistration =
                    registry.register(Servlet.class, servlet , props);
        }
    }

    /* (non-Javadoc)
     * @see com.massyframework.assembly.spi.ReadyingHandler#unreadyed()
     */
    @Override
    public void unreadyed() throws ReadyingException {
        this.unexportService();
    }

    /**
     * 撤销服务输出
     */
    protected synchronized void unexportService() {
        if (this.servletRegistration != null){
            this.servletRegistration.unregister();
            this.servletRegistration = null;
        }
    }

    /**
     * 在Web场景下注册的组件是否已准备就绪？<br>
     * Servlet的实例化要视注册时的选项，如果是首次请求时才实例化，那在发起首次请求前，本方法只能返回<code>false</code>
     *
     * @return {@link boolean},返回<code>true</code>表示已就绪，否则返回<code>false</code>
     */
    @Override
    public boolean isReady() {
        if (!this.runAtJ2EE) {
            return false;
        }

        return this.config != null;
    }
    
    
	
	@Override
	public void doStarting() throws Exception {
		try {
			super.doStarting();
			ScriptEngine engine = getScriptEngine();
        	engine.put("config", config);
        	
        	Object func = engine.get("httpproc");
        	if (func == null) {
        		throw new ScriptException("cannot found javascript function: httpproc");
        	}
        	
        	Servlet servlet = this.servletRegistration.getService(this.getAssociatedAssembly());
        	((Bridge)servlet).setScriptEngine(engine);
		}catch(Exception e) {
			if (LOG.isWarnEnabled()) {
				LOG.warn(e.getMessage(), e);
			}
		}
	}

	@Override
	public void doStopped() throws Exception {
		super.doStopped();
	}

	/**
     * 根据<code>assembly</code>获取{@link ServiceRepository}
     * @param assembly {@link Assembly}, 装配件
     * @return {@link ServiceRepository}
     */
    protected ServiceRepository getServiceRepository(Assembly assembly){
        return ServiceRepository.retrieveFrom(assembly);
    }

    /**
     * ServletBridge在DelegateServlet和DispatcherServletEx之间的连接桥
     */
    private class Bridge implements Servlet {
    	
    	private ScriptEngine engine;

        @Override
        public void init(ServletConfig config) throws ServletException {
        	ScriptEngineServletContextInitializer.this.config = new ConfigWrapper(config);
            try {
            	ScriptEngineServletContextInitializer.this.getLifecycleManager().start();
            } catch (Exception e) {
                throw new ServletException(e.getMessage(), e);
            }
        }


        @Override
        public ServletConfig getServletConfig() {
            return config;
        }

        @Override
        public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {        	
        	if (this.engine != null) {
        		Invocable invocable= (Invocable)this.engine;
            	try {
            		invocable.invokeFunction("httpproc", req, res);
            		if (res.isCommitted()) {
            			
            		}
				} catch (Exception e) {
					throw new ServletException(e.getMessage(), e);
				}
        	}else {
            	throw new ServletException("script engine not readied.");
            }
        }

        @Override
        public String getServletInfo() {
            return null;
        }
        
        public void setScriptEngine(ScriptEngine engine) {
        	this.engine = engine;
        }

        @Override
        public void destroy() {
            try {
            	ScriptEngineServletContextInitializer.this.getLifecycleManager().stop();
            	ScriptEngineServletContextInitializer.this.config = null;
            } catch (Exception e) {
                logError(e.getMessage(), e);
            }
        }

    }
    
    private class ConfigWrapper implements ServletConfig {
    	
    	private ServletConfig config;
    	
    	public ConfigWrapper(ServletConfig config) {
    		this.config = config;
    	}

		@Override
		public String getServletName() {
			return this.config.getServletName();
		}

		@Override
		public ServletContext getServletContext() {
			return this.config.getServletContext();
		}

		@Override
		public String getInitParameter(String name) {
			if (name.equals(DelegateServlet.IGNORE_INIT_ERROR)) {
				return "true";
			}
			return this.config.getInitParameter(name);
		}

		@Override
		public Enumeration<String> getInitParameterNames() {
			return this.config.getInitParameterNames();
		}
    }
}
